// util.hpp : 工具模块，存放其他模块可能使用到的公共方法
#pragma once

#include <iostream>
#include <string>      //std::string \ to_string()
#include <vector>      //std::vector
#include <atomic>      //std::atomic_uint : C++11里增加的原子性计数器
#include <fstream>     //std::ofstream \ std::ifstream
#include <sys/types.h> //stat(路径，输出型参数)
#include <sys/stat.h>  //stat()
#include <unistd.h>    //stat()
#include <ctime>       //time() \ localtime() \ strftime()
#include <sys/time.h>  //gettimeofday()
#include <boost/algorithm/string.hpp>  //boost::split() : 切割字符串

namespace ns_util
{

    const std::string temp_path = "./temp/"; // 路径（放在外面是为了便于修改）

    /*路径生成工具类：生产路径+后缀的完整文件名*/
    class PathUtil
    {
    public:
        /**********************************************************
         * 给文件名添加路径和后缀（使用C++字符串的拼接）
         *  构建源文件路径+后缀的完整文件名：1234 -> ./temp/1234.cpp
         》将对外提供的方法设置为static，这样不创建对象也能调用
         **********************************************************/
        static std::string AddSuffix(const std::string &file_name, const std::string &suffix)
        {
            std::string path_name = temp_path; // 添加路径
            path_name += file_name;
            path_name += suffix; // 添加后缀
            return path_name;
        }

        // 编译时需要的文件的文件名
        /*构建 要编译文件 的完整路径+后缀名*/
        static std::string Src(const std::string &file_name)
        {
            return AddSuffix(file_name, ".cpp");
        }
        /*构建 可执行程序 的完整路径+后缀名*/
        static std::string Exe(const std::string &file_name)
        {
            return AddSuffix(file_name, ".exe");
        }
        /*构建 编译错误文件 的完整路径+后缀名*/
        static std::string CompilerError(const std::string &file_name)
        {
            return AddSuffix(file_name, ".compile_error");
        }

        // 运行时需要的临时文件的文件名
        /*构建 程序标准输入 的完整路径+后缀名*/
        static std::string Stdin(const std::string &file_name)
        {
            return AddSuffix(file_name, ".stdin");
        }
        /*构建 程序标准输出 的完整路径+后缀名*/
        static std::string Stdout(const std::string &file_name)
        {
            return AddSuffix(file_name, ".stdout");
        }
        /*构建 程序标准错误 的完整路径+后缀名*/
        static std::string Stderr(const std::string &file_name)
        {
            return AddSuffix(file_name, ".stderr");
        }
    };

    /*时间类：有关时间获取的操作*/
    class TimeUtil
    {
    public:
        /*获取标准时间:年月日时分秒*/
        static std::string GetTime()
        {
            // 使用C库函数time()来获取时间戳
            time_t timestamp = time(nullptr); // 获取时间戳
            struct tm *timeinfo;              // 保存时间信息的结构体
            char tmbuffer[128] = {0};         // 保存数据信息的字符串
            timeinfo = localtime(&timestamp); // 用时间戳计算出时间结构体的参数
            strftime(tmbuffer, sizeof(tmbuffer), "%Y/%m/%d %H:%M:%S", timeinfo);

            // char* 可以直接转为string.
            return tmbuffer;
        }

        /*获取秒时间戳*/
        static std::string GetTimeStamp()
        {
            // 使用系统调用接口gettimeofda()来获取时间戳
            struct timeval _time;
            gettimeofday(&_time, nullptr);
            //_time.tv_sec : 累计到现在的秒数。
            return std::to_string(_time.tv_sec);
        }
        /*获取毫秒时间戳*/
        static std::string GetTimeMs()
        {
            // 使用系统调用接口gettimeofda()来获取时间戳
            struct timeval _time;
            gettimeofday(&_time, nullptr);
            //_time.tv_sec : 累计到现在的秒数。
            //_time.tv_usec : 累计到现在的微秒数。
            return std::to_string(_time.tv_sec * 1000 + _time.tv_usec / 1000);
        }
    };

    /*文件工具类：有关文件的操作*/
    class FileUtil
    {
    public:
        /**************************************************************
         * 判断文件是否存在
         *  传入一个文件的路径，通过系统调用接口stat()来判断文件是否存在
         *  也可以系统调用open()通过读方式打开文件来判断，打开失败表示文件不存在。
         》返回布尔值的方法以 is 或 are 开头（命名函数的技巧）
         **************************************************************/
        static bool IsFileExists(const std::string &path_name)
        {
            /*****************************************************************
             * stat(路径，输出型参数用来接收文件属性) :
             * 用来获取文件属性，获取成功返回0，失败返回-1，获取成功表示文件存在。
             *****************************************************************/
            struct stat st;
            if (stat(path_name.c_str(), &st) == 0)
            {
                // 获取属性成功，文件已经存在
                return true;
            }

            return false;
        }

        /**************************************************************************
         * 生成一个具有唯一性的文件名，用 毫秒级时间戳+原子性递增的静态变量 来保证唯一性。
         * uniq是英文unique(唯一)的缩写
         **************************************************************************/
        static std::string UniqFileName()
        {
            //获取定义在静态区，具有原子性的计数器
            static std::atomic_uint id(0);//atomic : C++11里增加的原子性计数器
            id++;
            // 获取毫秒级时间戳
            std::string ms = TimeUtil::GetTimeMs();
            //毫秒级时间戳+原子性递增的静态变量保证唯一性。
            std::string uniq_id = std::to_string(id);//to_string:将数字转化为字符串
            return ms + "_" + uniq_id;
        }

        /****************************
         * 往文件中写入内容
         * 参数：
         *     target  : 目标文件名
         *     content : 要写入的内容
         ****************************/
        static bool WriteFile(const std::string &target, const std::string &content)
        {
            std::ofstream out(target); //以写方式打开文件
            if (!out.is_open()) //文件如果打开失败
            {
                return false;
            }
            out.write(content.c_str(), content.size());//向文件中写入内容，参数:(要写的内容，内容的大小)
            out.close();//关闭文件
            return true;
        }
        /*********************************************
         * 读取文件内容
         * 参数：
         *   target  : 目标文件名
         *   content : 输出型参数，从文件中读到的内容
         *   keep    : 是否保留文件中的换行符"\n"
         *********************************************/
        static bool ReadFile(const std::string &target, std::string *content, bool keep = false)
        {
            (*content).clear(); //清空输出型参数

            std::ifstream in(target); //以读方式打开文件
            if (!in.is_open()) //文件如果打开失败
            {
                return false;
            }
            std::string line;
            // getline:不保存行分割符,有些时候需要保留\n.
            while (std::getline(in, line)) // 按行从文件中读取内容
            {
                (*content) += line; //向输出型参数里写入文件内容
                (*content) += (keep ? "\n" : ""); //保留换行符
                //line.clear();
            }
            in.close();//关闭文件
            return true;
        }
    };

    /*字符串工具类*/
    class StringUtil
    {
    public:
        /****************************************
         * 描述：切分字符串
         * 参数：
         *     str    : 要切分的字符串
         *     target : 输出型，保存切分完毕的结果
         *     sep    : 指定的分割符
         * **************************************/
        static void SplitString(const std::string &str, std::vector<std::string> *target, const std::string &sep)
        {
            //boost split
            boost::split((*target), str, boost::is_any_of(sep), boost::algorithm::token_compress_on);
        }
    };

}